// ==================================================================================
// Shared Genomics Project MPI Codebase
// Version 1.0 30/04/2010
//
// (c) 2010 University of Manchester all rights reserved
//
// This file is distributed under the GNU General Public License, Version 2.  
// Please see the file COPYING.txt for more details
// ==================================================================================

/*!
\file
\brief Implementation of SNP-Major Genomic Data-set (PLINK) 
*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "copyfile.h"
#include "mapfile.h"
#include "locus.h"
#include "utils.h"
#include "suffixes.h"
#include "options.h"
#include "csnp.h"

int mapfile_init(struct mapfile *m) {
	if (m != NULL) {
		m->locus = NULL;
		m->nLocus = 0;
		m->locus_available = 0;

		m->SNP = NULL;
		m->nSNP = 0;
		
		return 1;
	}

	return 0;
}

#define MAX_MAP_LINE_LENGTH 256
#define MAX_TOKEN_LENGTH 80
#define MESSAGE_BUFFER_LENGTH 1024
#define MAX_NUMBER_EXPECTED_TOKENS_MAP 4
#define INITIAL_ITEM_ARRAY_SIZE 500000
#define MIN_GROWTH_PERCENTAGE 5

int mapfile_read(struct mapfile *m, struct selected_options *ops, struct chromosomes *chromos) {
	FILE* fin = NULL;
	BOOL isMapOpen = FALSE;
	char message[MESSAGE_BUFFER_LENGTH];
	char line[MAX_MAP_LINE_LENGTH];
	char token[MAX_TOKEN_LENGTH];
	int lineCount = 0, tokenCount = 0;
	char tokens[MAX_NUMBER_EXPECTED_TOKENS_MAP][MAX_TOKEN_LENGTH];
	int c = 0, i = 0;
	
	if (m == NULL || ops == NULL || chromos == NULL) return 0;
	
	// Read in the input MAP file as read from options structure.
	if ((fin = fopen(ops->szMapFileName, "r")) != NULL)  isMapOpen = TRUE;
	else return 0;
	
	if (isMapOpen) {
		if(!mapfile_alloc_locus(INITIAL_ITEM_ARRAY_SIZE, m)) {
			fclose(fin);
			return 0;
		}
		
		while(fgets(line, MAX_MAP_LINE_LENGTH, fin) != NULL) {
			struct locus *loc = NULL;
			long int inc = 0;
			char chr[MAX_TOKEN_LENGTH]; // chromozone
			char *p = NULL, *q = NULL;

			chr[0] = '\0';
			lineCount++;
			if (strlen(line) == 0) continue;

			// Tokenise the line expecting 3/4 tokens.
			p = line;
			tokenCount = 0;
			while (*p) {
				q = token;
				while((*p != ' ' && *p != '\t' && *p != ',' && *p != '\r' && *p != '\n') && *p) { 
					*q = *p;
					q++; p++;
				}
				if (*p) p++; // advance past the space
				*q = '\0'; // NULL terminate the token

				if (strlen(token) == 0) continue;
				
				tokens[tokenCount][0] = '\0';
				if (token != NULL && (tokenCount >= 0 && tokenCount < MAX_NUMBER_EXPECTED_TOKENS_MAP))
					strcpy(tokens[tokenCount], token);

				tokenCount++;
			}
				
			if (tokenCount == 0) continue;
			else if ((ops->isMap3File && tokenCount != 3) || (!ops->isMap3File && tokenCount != 4)) {
				strcpy(message, "Problem with map file line:");
				strcat(message, line);
				error(message);
			} 

			// Create and assign a locus entry as read from the map file.
			loc = locus_create();
			if (loc == NULL) continue;
			if (ops->isGvar) loc->isVariant = TRUE;

			// Assign inputted tokens to locus/variant object.
			if(tokens[0] != NULL) strcpy(chr, tokens[0]);
			locus_set_name(loc, tokens[1]);

			if (ops->isMap3File) {
				loc->pos = 0;
				loc->bp = (long int) atoi(tokens[2]);
			} else {
				loc->pos = atof(tokens[2]);
				loc->bp = (long int) atoi(tokens[3]);
			}
			
			inc = loc->bp;

			if (!ops->isCmMap && loc->pos > 50 && ops->verbose)
				error("looks like you need to specify --cm ?? ");

			// Convert cM to M map distances
			if (ops->isCmMap) loc->pos /= 100;

			// Chromosome coding
			loc->chr = chromosomes_find(chr, chromos);
			if (loc->chr == -1) loc->chr = 0;

			// Use the frequency slot temporarily to 
			// store order information
			loc->freq = c++;

			if (strlen(loc->name) > 0) mapfile_insert_locus(loc, m);
		}
		fclose(fin);
		
		// Log messages detailing input file contents.
		sprintf(message, "%i markers to be included from %s", m->nLocus, ops->szMapFileName);
		if(ops->verbose) print_log(message);
		if (ops->isGvar && ops->verbose) print_log("Read in as generic variants, rather than SNPS");

		if (m->nLocus == 0) return 0;
		
		if (ops->isSnpMajor && !ops->loadGvar) {
			m->SNP = (struct csnp**) malloc(sizeof(struct csnp*) * m->nLocus);
			if (m->SNP == NULL) 
				return 0;
			else {
				for (i = 0; i < m->nLocus; i++) {
					m->SNP[i] = csnp_create();
					m->nSNP++;
				}
			}
		} 
		
		return 1;
	}
	
	return 0;
}

int mapfile_free_attributes(struct mapfile *m) {
	int i;

	if (m != NULL) {
		// Free the Loci array.
		if (m->locus != NULL) {
			if (m->nLocus > 0) {
				for (i = 0; i < m->nLocus; i++) {
					locus_free_attributes(m->locus[i]);
					if (m->locus[i] != NULL) free(m->locus[i]);
				}
			}
			free(m->locus);
			m->nLocus = 0;
			m->locus_available = 0; 
			m->locus = NULL;
		}

		// Free the SNP (major) array.
		if (m->nSNP > 0) {
			for (i = 0; i < m->nSNP; i++) {
				csnp_free_attributes(m->SNP[i]);
				free(m->SNP[i]);
			}
			free(m->SNP);
			m->SNP = NULL;
			m->nSNP = 0;
		} 

		return 1;
	} 

	return 0;
}

int mapfile_alloc_locus(int size, struct mapfile *m) {
	if (m != NULL && size > 0) {
		struct locus **p = (struct locus**) malloc(sizeof(struct locus*) * size);

		if (p != NULL) {
			m->locus = p;
			m->nLocus = 0;
			m->locus_available = size;

			return 1;
		} 
	}
		
	return 0;
}

int mapfile_realloc_locus(int size, struct mapfile *m) {
	struct locus **pNew = NULL, **pOld = NULL;
	
	if (m != NULL && size > 0) { 
		if (m->locus_available == 0) return 0; // Haven't allocated
		else {
			pOld = m->locus;
			pNew = (struct locus**) realloc(m->locus, sizeof(struct locus*) * size);
			if (pNew != NULL) {
				m->locus = pNew;
				m->locus_available = size;

				return 1;
			} else {
				m->locus = pOld;
				return 0;
			}
		}
	}

	return 0;
}

int mapfile_insert_locus(struct locus *value, struct mapfile *m) {
	if (m != NULL) {
		if (m->locus_available == 0) return 0;
		if (m->nLocus + 1 > m->locus_available) {
			if(!mapfile_realloc_locus(m->locus_available + INITIAL_ITEM_ARRAY_SIZE, m))
				return 0;
		} 
		
		m->locus[m->nLocus] = value;
		m->nLocus++;

		return 1;
	}
		
	return 0;
}

struct mapfile* mapfile_create() {
	struct mapfile *m = NULL;

	m = (struct mapfile*) malloc(sizeof(struct mapfile));
	mapfile_init(m);

	return m;
}

int mapfile_alloc_SNP(int nSNPs, int nSamples, struct mapfile *m) {
	BOOL jobDone = FALSE;

	if (m == NULL || nSNPs <= 0) return 0;
	else if (m->nSNP != 0 || m->SNP != NULL) return 0;
	
	m->SNP = (struct csnp**) malloc(sizeof(struct csnp*) * nSNPs);
	if (m->SNP == NULL) return 0;
	else {
		int i;
		for (i = 0; i < nSNPs; i++) {
			m->SNP[i] = csnp_create();
			if (m->SNP[i] != NULL) m->nSNP++;
		}
		if (m->nSNP == nSNPs) {
			int one_and_two_allocated = 0;
			for (i = 0; i < m->nSNP; i++) {
				if (csnp_alloc_one(m->SNP[i], nSamples) && csnp_alloc_two(m->SNP[i], nSamples)) one_and_two_allocated++;
			}
			if (one_and_two_allocated == m->nSNP) jobDone = TRUE;
		}
	}

	if (jobDone) return 1;
	else return 0;
}

int mapfile_copy_and_read(struct mapfile *m, struct selected_options *ops, struct chromosomes *chromos, int rank) {
	BOOL jobDone = FALSE;
	char localFilepath[FILE_PATH_LENGTH], remoteFilepath[FILE_PATH_LENGTH];

	if (m == NULL || ops == NULL || chromos == NULL) return 0;

	strcpy(remoteFilepath, ops->szMapFileName);
	set_datafile_path(localFilepath, ops->ldir, ops->szRunId, rank, SUFFIX_MAP_FILE);
	if (copyFile(remoteFilepath, localFilepath)) {
		strcpy(ops->szMapFileName, localFilepath);
		if (mapfile_read(m, ops, chromos)) 
			if (deleteFile(localFilepath)) jobDone = TRUE;
	}

	if (jobDone) return 1;
	else return 0;
}
